package your.rfgui.extgui.controller.table;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.faces.event.AjaxBehaviorEvent;
import org.apache.log4j.Logger;
import org.richfaces.component.UIExtendedDataTable;
import your.rfgui.extgui.data.intf.DataItem;

/**
 * View controller managing a table list.
 */
public abstract class AbstractTableController {

   private static final Logger log = Logger.getLogger(AbstractTableController.class);
   /** internal parameter */
   private static final boolean recursiveListChildren = true;
   /** list parent: all children (may be recursive) of this item are displayed in the list */
   private DataItem listParent;
   /** sequential list of data items displayed in table list */
   private List<TableViewNode> tableList;
   /** selection object managed by GUI frame work (here: rich faces) */
   /** each element represents one selected row by means of an Integer(rowIndex) */
   private Collection<Object> selectionRowKeys = new ArrayList<Object>();
   /** the currently selected item in the table list if any */
   private List<TableViewNode> selectedViewNodes = new ArrayList<TableViewNode>();

   /**
    * Called by outside (e.g. tree controller)
    * @return current list parent
    */
   public DataItem getListParent() {
      return listParent;
   }

   /**
    * Called by view
    * @return list of data items of the table list
    */
   public List<TableViewNode> getTableList() {
      return tableList;
   }

   /**
    * Called by outside (e.g tree controller) to change the list parent in order to update
    * the view. Table list is even rebuild and updated in view if list parent do not change
    * @param listParent the new list parent
    */
   public void setListParent(DataItem listParent) {
      log.info("setListParent: " + listParent);
      this.listParent = listParent;

      /** renew entries in table list */
      tableList = new ArrayList<TableViewNode>();

      List<? extends DataItem> dataItems = getViewChildren(listParent);
      for (DataItem dataItem : dataItems) {
         TableViewNode tableNode = new TableViewNode(dataItem);
         tableList.add(tableNode);
         if (recursiveListChildren) {
            populate(tableNode, getViewChildren(dataItem));
         }
      }
      unselectTable();
   }

   /**
    * Internal helper to build table list recursively
    */
   private void populate(TableViewNode tableNodeParent, List<? extends DataItem> dataItems) {
      for (DataItem dataItem : dataItems) {
         TableViewNode tableNode = new TableViewNode(tableNodeParent, dataItem);
         tableList.add(tableNode);
         populate(tableNode, getViewChildren(dataItem));
      }
   }

   /**
    * Used by View. Called on item selection in table.
    *  <rich:extendedDataTable ... selection="#{bean.table.selectionRowKeys}">
    * Rich Faces uses the returned collection also to change the selection
    */
   public Collection<Object> getSelectionRowKeys() {
      log.info("getSelectionRowKeys: " + getSelectionRowKeysString(selectionRowKeys));
      return selectionRowKeys;
   }

   /**
    * Used by View. Called for item selection in table.
    *  <rich:extendedDataTable ... selection="#{bean.table.selectionRowKeys}">
    */
   public void setSelectionRowKeys(Collection<Object> selectionRowKeys) {
      /** is not explicitely called by framework, obviously the element is added to the collection retrieved by getSelection() */
      this.selectionRowKeys = selectionRowKeys;
   }

   /**
    * Used by View. Called on selection change. Triggered by:
    * <rich:extendedDataTable ...>
    * <a4j:ajax execute="@this" event="selectionchange" listener="#{bean.table.processSelectionChange}" render="table-footer"/>
    */
   public void processSelectionChange(AjaxBehaviorEvent event) {
      log.info("processSelectionChange");

      UIExtendedDataTable dataTable = (UIExtendedDataTable) event.getComponent();
      Object originalRowKey = dataTable.getRowKey();

      /** use rich faces selection keys provided to get/setSelectionRowKeys() */
      /** more than one item may be selected (controlled by selectionMode-attribute) */
      selectedViewNodes.clear();
      for (Object selectionKey : selectionRowKeys) {
         dataTable.setRowKey(selectionKey);
         assert (dataTable.isRowAvailable());
         selectedViewNodes.add((TableViewNode) dataTable.getRowData());
      }
      dataTable.setRowKey(originalRowKey);

      for (TableViewNode selectedViewNode : selectedViewNodes) {
         log.info("processSelectionChange: selectedViewNode: " + selectedViewNode);
      }
   }

   /**
    * Used by application.
    * Get the currently selected view node.
    */
   public TableViewNode getSelectedViewNode() {
      if (selectedViewNodes.size() > 0) {
         log.info("getSelectedViewNode: " + selectedViewNodes.get(0));
         return selectedViewNodes.get(0);
      } else {
         log.info("getSelectedViewNode - null");
         return null;
      }
   }

   /**
    * Used by application.
    * Set the currently selected view node, change also selection in the GUI component.
    */
   public void setSelectedViewNode(TableViewNode selectedViewNode) {
      log.info("setSelectedViewNode: " + selectedViewNode);

      selectedViewNodes.clear();
      selectedViewNodes.add(selectedViewNode);

      /** set rich faces selection keys: each element represents one selected row by means of an Integer(rowIndex) */
      selectionRowKeys.clear();
      int selectionIndex = tableList.indexOf(selectedViewNode);
      if (selectionIndex != -1) {
         selectionRowKeys.add(new Integer(selectionIndex));
         log.trace("setSelectedViewNode: " + getSelectionRowKeysString(selectionRowKeys));
      }
   }

   /**
    * Used by application.
    * Get currently selected data item in table list.
    */
   public DataItem getSelectedDataItem() {
      /** for now considering only single selection */
      if (selectedViewNodes.size() > 0) {
         DataItem selectedDataItem = selectedViewNodes.get(0).getDataItem();
         log.info("getSelectedDataItem: " + selectedDataItem);
         return selectedDataItem;
      }
      log.info("getSelectedDataItem - null");
      return null;
   }

   /**
    * Used by application.
    * Set the currently selected view node for the given data item, change also selection in the GUI component.
    */
   public void setSelectedDataItem(DataItem selectedDataItem) {
      setSelectedViewNode(getViewNode(selectedDataItem));
   }

   /**
    * Used by application.
    * Deselect if an item is selected in the table
    * e.g. is called by tree controller if dummy root without data representation is
    * clicked by user to explicitly deselect the currently selected item
    */
   public void unselectTable() {
      log.info("unselectTable");
      selectedViewNodes.clear();
      selectionRowKeys.clear();
   }

   /**
    * Used by application.
    * Look the view node for the given data item
    */
   public TableViewNode getViewNode(DataItem dataItem) {
      for (TableViewNode viewNode : tableList) {
         if (viewNode.getDataItem().identical(dataItem)) {
            return viewNode;
         }
      }
      return null;
   }

   /**
    * Helper for stringifying the rich faces selection keys
    */
   private String getSelectionRowKeysString(Collection<Object> selectionRowKeys) {
      StringBuilder rowKeysStr = new StringBuilder(25);

      for (Object rowKey : selectionRowKeys) {
         if (rowKeysStr.length() > 0) {
            rowKeysStr.append(',');
         }
         rowKeysStr.append(rowKey.toString());
      }
      return rowKeysStr.toString();
   }

   /**
    * Returns the children data item list for the given data item
    */
   abstract List<? extends DataItem> getViewChildren(DataItem dataItem);
}
